]> dgit.raspbian.org Git - gnome-shell.git/commitdiff
status/network: Split out WirelessNetwork class
authorFlorian Müllner <fmuellner@gnome.org>
Sat, 23 Jul 2022 23:11:32 +0000 (01:11 +0200)
committerFlorian Müllner <fmuellner@gnome.org>
Sun, 7 Aug 2022 20:41:09 +0000 (22:41 +0200)
Different access points can belong to the same wireless network. As
NetworkManager doesn't handle this for us, we need to track networks
ourselves, and we currently do this using ad-hoc object literals and
monkey-patching.

Clean this up by factoring out a proper WirelessNetwork class, and
associate them to items with a map.

Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/2406>

js/ui/status/network.js

index f458a96e973bc652dbc2d3f46dcf51f7dc094cb6..be6ecd1221a4723dc40c08aefa3df17e34e93c3e 100644 (file)
@@ -13,7 +13,8 @@ const ModemManager = imports.misc.modemManager;
 const Rfkill = imports.ui.status.rfkill;
 const Util = imports.misc.util;
 
-const { loadInterfaceXML } = imports.misc.fileUtils;
+const {loadInterfaceXML} = imports.misc.fileUtils;
+const {registerDestroyableType} = imports.misc.signalTracker;
 
 Gio._promisify(Gio.DBusConnection.prototype, 'call');
 Gio._promisify(NM.Client, 'new_async');
@@ -27,15 +28,6 @@ const NMConnectionCategory = {
     VPN: 'vpn',
 };
 
-const NMAccessPointSecurity = {
-    NONE: 1,
-    WEP: 2,
-    WPA_PSK: 3,
-    WPA2_PSK: 4,
-    WPA_ENT: 5,
-    WPA2_ENT: 6,
-};
-
 var MAX_DEVICE_ITEMS = 4;
 
 // small optimization, to avoid using [] all the time
@@ -662,6 +654,241 @@ var NMDeviceBluetooth = class extends NMConnectionDevice {
     }
 };
 
+const WirelessNetwork = GObject.registerClass({
+    Properties: {
+        'name': GObject.ParamSpec.string(
+            'name', '', '',
+            GObject.ParamFlags.READABLE,
+            ''),
+        'icon-name': GObject.ParamSpec.string(
+            'icon-name', '', '',
+            GObject.ParamFlags.READABLE,
+            ''),
+        'secure': GObject.ParamSpec.boolean(
+            'secure', '', '',
+            GObject.ParamFlags.READABLE,
+            false),
+        'is-active': GObject.ParamSpec.boolean(
+            'is-active', '', '',
+            GObject.ParamFlags.READABLE,
+            false),
+    },
+    Signals: {
+        'destroy': {},
+    },
+}, class WirelessNetwork extends GObject.Object {
+    static _securityTypes =
+        Object.values(NM.UtilsSecurityType).sort((a, b) => b - a);
+
+    _init(device) {
+        super._init();
+
+        this._device = device;
+
+        this._device.connectObject(
+            'notify::active-access-point', () => this.notify('is-active'),
+            this);
+
+        this._accessPoints = new Set();
+        this._connections = [];
+        this._name = '';
+        this._ssid = null;
+        this._bestAp = null;
+        this._mode = 0;
+        this._securityType = NM.UtilsSecurityType.NONE;
+    }
+
+    get _strength() {
+        return this._bestAp?.strength ?? 0;
+    }
+
+    get name() {
+        return this._name;
+    }
+
+    get icon_name() {
+        if (this._mode === NM80211Mode.ADHOC)
+            return 'network-workgroup-symbolic';
+
+        if (!this._bestAp)
+            return '';
+
+        return `network-wireless-signal-${signalToIcon(this._bestAp.strength)}-symbolic`;
+    }
+
+    get secure() {
+        return this._securityType !== NM.UtilsSecurityType.NONE;
+    }
+
+    get is_active() {
+        return this._accessPoints.has(this._device.activeAccessPoint);
+    }
+
+    hasAccessPoint(ap) {
+        return this._accessPoints.has(ap);
+    }
+
+    hasAccessPoints() {
+        return this._accessPoints.size > 0;
+    }
+
+    checkAccessPoint(ap) {
+        if (!ap.get_ssid())
+            return false;
+
+        const secType = this._getApSecurityType(ap);
+        if (secType === NM.UtilsSecurityType.INVALID)
+            return false;
+
+        if (this._accessPoints.size === 0)
+            return true;
+
+        return this._ssid.equal(ap.ssid) &&
+            this._mode === ap.mode &&
+            this._securityType === secType;
+    }
+
+    /**
+     * @param {NM.AccessPoint} ap - an access point
+     * @returns {bool} - whether the access point was added
+     */
+    addAccessPoint(ap) {
+        if (!this.checkAccessPoint(ap))
+            return false;
+
+        if (this._accessPoints.size === 0) {
+            this._ssid = ap.get_ssid();
+            this._mode = ap.mode;
+            this._securityType = this._getApSecurityType(ap);
+            this._name = NM.utils_ssid_to_utf8(this._ssid.get_data()) || '<unknown>';
+
+            this.notify('name');
+            this.notify('secure');
+        }
+
+        const wasActive = this.is_active;
+        this._accessPoints.add(ap);
+
+        ap.connectObject(
+            'notify::strength', () => this._updateBestAp(),
+            this);
+        this._updateBestAp();
+
+        if (wasActive !== this.is_active)
+            this.notify('is-active');
+
+        return true;
+    }
+
+    /**
+     * @param {NM.AccessPoint} ap - an access point
+     * @returns {bool} - whether the access point was removed
+     */
+    removeAccessPoint(ap) {
+        const wasActive = this.is_active;
+        if (!this._accessPoints.delete(ap))
+            return false;
+
+        this._updateBestAp();
+
+        if (wasActive !== this.is_active)
+            this.notify('is-active');
+
+        return true;
+    }
+
+    /**
+     * @param {WirelessNetwork} other - network to compare with
+     * @returns {number} - the sort order
+     */
+    compare(other) {
+        // place known connections first
+        const cmpConnections = other.hasConnections() - this.hasConnections();
+        if (cmpConnections !== 0)
+            return cmpConnections;
+
+        const cmpAps = other.hasAccessPoints() - this.hasAccessPoints();
+        if (cmpAps !== 0)
+            return cmpAps;
+
+        // place stronger connections first
+        const cmpStrength = other._strength - this._strength;
+        if (cmpStrength !== 0)
+            return cmpStrength;
+
+        // place secure connections first
+        const cmpSec = other.secure - this.secure;
+        if (cmpSec !== 0)
+            return cmpSec;
+
+        // sort alphabetically
+        return GLib.utf8_collate(this._name, other._name);
+    }
+
+    hasConnections() {
+        return this._connections.length > 0;
+    }
+
+    checkConnections(connections) {
+        const aps = [...this._accessPoints];
+        this._connections = connections.filter(
+            c => aps.some(ap => ap.connection_valid(c)));
+    }
+
+    canAutoconnect() {
+        const canAutoconnect =
+            this._securityTypes !== NM.UtilsSecurityType.WPA_ENTERPRISE &&
+            this._securityTypes !== NM.UtilsSecurityType.WPA2_ENTERPRISE;
+        return canAutoconnect;
+    }
+
+    activate() {
+        const [ap] = this._accessPoints;
+        let [conn] = this._connections;
+        if (conn) {
+            this._device.client.activate_connection_async(conn, this._device, null, null, null);
+        } else if (!this.canAutoconnect()) {
+            launchSettingsPanel('wifi', 'connect-8021x-wifi',
+                this._getDeviceDBusPath(), ap.get_path());
+        } else {
+            conn = new NM.SimpleConnection();
+            this._device.client.add_and_activate_connection_async(
+                conn, this._device, ap.get_path(), null, null);
+        }
+    }
+
+    destroy() {
+        this.emit('destroy');
+    }
+
+    _getDeviceDBusPath() {
+        // nm_object_get_path() is shadowed by nm_device_get_path()
+        return NM.Object.prototype.get_path.call(this._device);
+    }
+
+    _getApSecurityType(ap) {
+        const {wirelessCapabilities: caps} = this._device;
+        const {flags, wpaFlags, rsnFlags} = ap;
+        const haveAp = true;
+        const adHoc = ap.mode === NM80211Mode.ADHOC;
+        const bestType = WirelessNetwork._securityTypes
+            .find(t => NM.utils_security_valid(t, caps, haveAp, adHoc, flags, wpaFlags, rsnFlags));
+        return bestType ?? NM.UtilsSecurityType.INVALID;
+    }
+
+    _updateBestAp() {
+        const [bestAp] =
+            [...this._accessPoints].sort((a, b) => b.strength - a.strength);
+
+        if (this._bestAp === bestAp)
+            return;
+
+        this._bestAp = bestAp;
+        this.notify('icon-name');
+    }
+});
+registerDestroyableType(WirelessNetwork);
+
 var NMWirelessDialogItem = GObject.registerClass({
     Signals: {
         'selected': {},
@@ -669,7 +896,6 @@ var NMWirelessDialogItem = GObject.registerClass({
 }, class NMWirelessDialogItem extends St.BoxLayout {
     _init(network) {
         this._network = network;
-        this._ap = network.accessPoints[0];
 
         super._init({
             style_class: 'nm-dialog-item',
@@ -681,9 +907,7 @@ var NMWirelessDialogItem = GObject.registerClass({
         action.connect('clicked', () => this.grab_key_focus());
         this.add_action(action);
 
-        let title = ssidToLabel(this._ap.get_ssid());
         this._label = new St.Label({
-            text: title,
             x_expand: true,
         });
 
@@ -702,47 +926,43 @@ var NMWirelessDialogItem = GObject.registerClass({
         });
         this.add_child(this._icons);
 
-        this._secureIcon = new St.Icon({ style_class: 'nm-dialog-icon' });
-        if (this._ap._secType != NMAccessPointSecurity.NONE)
-            this._secureIcon.icon_name = 'network-wireless-encrypted-symbolic';
+        this._secureIcon = new St.Icon({
+            style_class: 'nm-dialog-icon',
+        });
         this._icons.add_actor(this._secureIcon);
 
-        this._signalIcon = new St.Icon({ style_class: 'nm-dialog-icon' });
+        this._signalIcon = new St.Icon({
+            style_class: 'nm-dialog-icon',
+        });
         this._icons.add_actor(this._signalIcon);
 
-        this._sync();
-    }
-
-    vfunc_key_focus_in() {
-        this.emit('selected');
-    }
-
-    _sync() {
-        this._signalIcon.icon_name = this._getSignalIcon();
-    }
-
-    updateBestAP(ap) {
-        this._ap = ap;
-        this._sync();
+        this._network.bind_property('icon-name',
+            this._signalIcon, 'icon-name',
+            GObject.BindingFlags.SYNC_CREATE);
+        this._network.bind_property('name',
+            this._label, 'text',
+            GObject.BindingFlags.SYNC_CREATE);
+        this._network.bind_property('is-active',
+            this._selectedIcon, 'visible',
+            GObject.BindingFlags.SYNC_CREATE);
+        this._network.bind_property_full('secure',
+            this._secureIcon, 'icon-name',
+            GObject.BindingFlags.SYNC_CREATE,
+            (bind, source) => [true, source ? 'network-wireless-encrypted-symbolic' : ''],
+            null);
     }
 
-    setActive(isActive) {
-        this._selectedIcon.opacity = isActive ? 255 : 0;
+    get network() {
+        return this._network;
     }
 
-    _getSignalIcon() {
-        if (this._ap.mode === NM80211Mode.ADHOC)
-            return 'network-workgroup-symbolic';
-        else
-            return `network-wireless-signal-${signalToIcon(this._ap.strength)}-symbolic`;
+    vfunc_key_focus_in() {
+        this.emit('selected');
     }
 });
 
 var NMWirelessDialog = GObject.registerClass(
 class NMWirelessDialog extends ModalDialog.ModalDialog {
-    static _securityTypes =
-        Object.values(NM.UtilsSecurityType).sort((a, b) => b - a);
-
     _init(client, device) {
         super._init({ styleClass: 'nm-dialog' });
 
@@ -758,7 +978,7 @@ class NMWirelessDialog extends ModalDialog.ModalDialog {
             'notify::hw-airplane-mode', () => this._syncView(),
             this);
 
-        this._networks = [];
+        this._networkItems = new Map();
         this._buildLayout();
 
         let connections = client.get_connections();
@@ -766,18 +986,25 @@ class NMWirelessDialog extends ModalDialog.ModalDialog {
             connection => device.connection_valid(connection));
 
         device.connectObject(
-            'access-point-added', this._accessPointAdded.bind(this),
-            'access-point-removed', this._accessPointRemoved.bind(this),
-            'notify::active-access-point', this._activeApChanged.bind(this), this);
-
-        // accessPointAdded will also create dialog items
-        let accessPoints = device.get_access_points() || [];
-        accessPoints.forEach(ap => {
-            this._accessPointAdded(this._device, ap);
-        });
+            'notify::active-access-point', () => this._updateSensitivity(),
+            'notify::available-connections', () => this._availableConnectionsChanged(),
+            'access-point-added', (d, ap) => {
+                this._addAccessPoint(ap);
+                this._syncNetworksList();
+                this._syncView();
+            },
+            'access-point-removed', (d, ap) => {
+                this._removeAccessPoint(ap);
+                this._syncNetworksList();
+                this._syncView();
+            },
+            this);
+
+        for (const ap of this._device.get_access_points())
+            this._addAccessPoint(ap);
 
         this._selectedNetwork = null;
-        this._activeApChanged();
+        this._availableConnectionsChanged();
         this._updateSensitivity();
         this._syncView();
 
@@ -813,24 +1040,16 @@ class NMWirelessDialog extends ModalDialog.ModalDialog {
         return GLib.SOURCE_CONTINUE;
     }
 
-    _activeApChanged() {
-        if (this._activeNetwork)
-            this._activeNetwork.item.setActive(false);
-
-        this._activeNetwork = null;
-        if (this._device.active_access_point) {
-            let idx = this._findNetwork(this._device.active_access_point);
-            if (idx >= 0)
-                this._activeNetwork = this._networks[idx];
-        }
-
-        if (this._activeNetwork)
-            this._activeNetwork.item.setActive(true);
-        this._updateSensitivity();
+    _availableConnectionsChanged() {
+        const connections = this._device.get_available_connections();
+        for (const net of this._networkItems.keys())
+            net.checkConnections(connections);
+        this._syncNetworksList();
     }
 
     _updateSensitivity() {
-        let connectSensitive = this._client.wireless_enabled && this._selectedNetwork && (this._selectedNetwork != this._activeNetwork);
+        const connectSensitive =
+            this._client.wireless_enabled && this._selectedNetwork && !this._selectedNetwork.isActive;
         this._connectButton.reactive = connectSensitive;
         this._connectButton.can_focus = connectSensitive;
     }
@@ -861,7 +1080,7 @@ class NMWirelessDialog extends ModalDialog.ModalDialog {
         } else {
             this._airplaneBox.hide();
 
-            this._noNetworksBox.visible = this._networks.length == 0;
+            this._noNetworksBox.visible = this._networkItems.size === 0;
         }
 
         if (this._noNetworksBox.visible)
@@ -971,265 +1190,85 @@ class NMWirelessDialog extends ModalDialog.ModalDialog {
     }
 
     _connect() {
-        let network = this._selectedNetwork;
-        if (network.connections.length > 0) {
-            let connection = network.connections[0];
-            this._client.activate_connection_async(connection, this._device, null, null, null);
-        } else {
-            let accessPoints = network.accessPoints;
-            if ((accessPoints[0]._secType == NMAccessPointSecurity.WPA2_ENT) ||
-                (accessPoints[0]._secType == NMAccessPointSecurity.WPA_ENT)) {
-                // 802.1x-enabled APs require further configuration, so they're
-                // handled in gnome-control-center
-                launchSettingsPanel('wifi', 'connect-8021x-wifi',
-                    this._getDeviceDBusPath(), accessPoints[0].get_path());
-            } else {
-                let connection = new NM.SimpleConnection();
-                this._client.add_and_activate_connection_async(connection, this._device, accessPoints[0].get_path(), null, null);
-            }
-        }
-
+        this._selectedNetwork?.activate();
         this.close();
     }
 
-    _getDeviceDBusPath() {
-        // nm_object_get_path() is shadowed by nm_device_get_path()
-        return NM.Object.prototype.get_path.call(this._device);
-    }
-
-    _notifySsidCb(accessPoint) {
-        if (accessPoint.get_ssid() != null) {
-            accessPoint.disconnectObject(this);
-            this._accessPointAdded(this._device, accessPoint);
-        }
-    }
-
-    _getApSecurityType(accessPoint) {
-        if (accessPoint._secType)
-            return accessPoint._secType;
-
-        const {wirelessCapabilities: caps} = this._device;
-        const {flags, wpaFlags, rsnFlags} = accessPoint;
-        const haveAp = true;
-        const adHoc = accessPoint.mode === NM80211Mode.ADHOC;
-        const bestType = NMWirelessDialog._securityTypes
-            .find(t => NM.utils_security_valid(t, caps, haveAp, adHoc, flags, wpaFlags, rsnFlags));
-
-        // cache the found value to avoid checking flags all the time
-        accessPoint._secType = bestType ?? NM.UtilsSecurityType.INVALID;
-        return accessPoint._secType;
-    }
-
-    _networkSortFunction(one, two) {
-        let oneHasConnection = one.connections.length != 0;
-        let twoHasConnection = two.connections.length != 0;
-
-        // place known connections first
-        // (-1 = good order, 1 = wrong order)
-        if (oneHasConnection && !twoHasConnection)
-            return -1;
-        else if (!oneHasConnection && twoHasConnection)
-            return 1;
-
-        let oneAp = one.accessPoints[0] || null;
-        let twoAp = two.accessPoints[0] || null;
-
-        if (oneAp != null && twoAp == null)
-            return -1;
-        else if (oneAp == null && twoAp != null)
-            return 1;
-
-        let oneStrength = oneAp.strength;
-        let twoStrength = twoAp.strength;
-
-        // place stronger connections first
-        if (oneStrength != twoStrength)
-            return oneStrength < twoStrength ? 1 : -1;
-
-        let oneHasSecurity = one.security != NMAccessPointSecurity.NONE;
-        let twoHasSecurity = two.security != NMAccessPointSecurity.NONE;
-
-        // place secure connections first
-        // (we treat WEP/WPA/WPA2 the same as there is no way to
-        // take them apart from the UI)
-        if (oneHasSecurity && !twoHasSecurity)
-            return -1;
-        else if (!oneHasSecurity && twoHasSecurity)
-            return 1;
-
-        // sort alphabetically
-        return GLib.utf8_collate(one.ssidText, two.ssidText);
-    }
-
-    _networkCompare(network, accessPoint) {
-        if (!network.ssid.equal(accessPoint.get_ssid()))
-            return false;
-        if (network.mode != accessPoint.mode)
-            return false;
-        if (network.security != this._getApSecurityType(accessPoint))
-            return false;
-
-        return true;
-    }
-
-    _findExistingNetwork(accessPoint) {
-        for (let i = 0; i < this._networks.length; i++) {
-            let network = this._networks[i];
-            for (let j = 0; j < network.accessPoints.length; j++) {
-                if (network.accessPoints[j] == accessPoint)
-                    return { network: i, ap: j };
-            }
-        }
-
-        return null;
-    }
-
-    _findNetwork(accessPoint) {
-        if (accessPoint.get_ssid() == null)
-            return -1;
-
-        for (let i = 0; i < this._networks.length; i++) {
-            if (this._networkCompare(this._networks[i], accessPoint))
-                return i;
-        }
-        return -1;
-    }
-
-    _checkConnections(network, accessPoint) {
-        this._connections.forEach(connection => {
-            if (accessPoint.connection_valid(connection) &&
-                !network.connections.includes(connection))
-                network.connections.push(connection);
-        });
-    }
-
-    _accessPointAdded(device, accessPoint) {
-        if (accessPoint.get_ssid() == null) {
+    _addAccessPoint(ap) {
+        if (ap.get_ssid() == null) {
             // This access point is not visible yet
             // Wait for it to get a ssid
-            accessPoint.connectObject('notify::ssid',
-                this._notifySsidCb.bind(this), this);
+            ap.connectObject('notify::ssid', () => {
+                if (!ap.ssid)
+                    return;
+                ap.disconnectObject(this);
+                this._addAccessPoint(ap);
+            }, this);
             return;
         }
 
-        let pos = this._findNetwork(accessPoint);
-        let network;
-
-        if (pos != -1) {
-            network = this._networks[pos];
-            if (network.accessPoints.includes(accessPoint)) {
-                log('Access point was already seen, not adding again');
-                return;
-            }
-
-            Util.insertSorted(network.accessPoints, accessPoint, (one, two) => {
-                return two.strength - one.strength;
-            });
-            network.item.updateBestAP(network.accessPoints[0]);
-            this._checkConnections(network, accessPoint);
-
-            this._resortItems();
-        } else {
-            network = {
-                ssid: accessPoint.get_ssid(),
-                mode: accessPoint.mode,
-                security: this._getApSecurityType(accessPoint),
-                connections: [],
-                item: null,
-                accessPoints: [accessPoint],
-            };
-            network.ssidText = ssidToLabel(network.ssid);
-            this._checkConnections(network, accessPoint);
-
-            let newPos = Util.insertSorted(this._networks, network, this._networkSortFunction);
-            this._createNetworkItem(network);
-            this._itemBox.insert_child_at_index(network.item, newPos);
+        let network = [...this._networkItems.keys()]
+            .find(n => n.checkAccessPoint(ap));
+
+        if (!network) {
+            network = new WirelessNetwork(this._device);
+            network.connectObject(
+                'notify::icon-name', () => this._syncNetworksList(),
+                'notify::is-active', () => this._syncNetworksList(),
+                this);
+            const item = this._createNetworkItem(network);
+            this._itemBox.add_child(item);
+            this._networkItems.set(network, item);
         }
 
-        this._queueSyncItemVisibility();
-        this._syncView();
-    }
-
-    _queueSyncItemVisibility() {
-        if (this._syncVisibilityId)
-            return;
-
-        this._syncVisibilityId = Meta.later_add(
-            Meta.LaterType.BEFORE_REDRAW,
-            () => {
-                const { hasWindows } = Main.sessionMode;
-                const { WPA2_ENT, WPA_ENT } = NMAccessPointSecurity;
-
-                for (const network of this._networks) {
-                    const [firstAp] = network.accessPoints;
-                    network.item.visible =
-                        hasWindows ||
-                        network.connections.length > 0 ||
-                        (firstAp._secType !== WPA2_ENT && firstAp._secType !== WPA_ENT);
-                }
-                this._syncVisibilityId = 0;
-                return GLib.SOURCE_REMOVE;
-            });
+        network.addAccessPoint(ap);
     }
 
-    _accessPointRemoved(device, accessPoint) {
-        let res = this._findExistingNetwork(accessPoint);
+    _removeAccessPoint(ap) {
+        const network = [...this._networkItems.keys()]
+            .find(n => n.removeAccessPoint(ap));
 
-        if (res == null) {
-            log('Removing an access point that was never added');
+        if (!network || network.hasAccessPoints())
             return;
-        }
 
-        let network = this._networks[res.network];
-        network.accessPoints.splice(res.ap, 1);
-
-        if (network.accessPoints.length == 0) {
-            network.item.destroy();
-            this._networks.splice(res.network, 1);
-        } else {
-            network.item.updateBestAP(network.accessPoints[0]);
-            this._resortItems();
-        }
-
-        this._syncView();
+        this._networkItems.get(network)?.destroy();
+        this._networkItems.delete(network);
+        network.destroy();
     }
 
-    _resortItems() {
-        let adjustment = this._scrollView.vscroll.adjustment;
-        let scrollValue = adjustment.value;
+    _syncNetworksList() {
+        const {hasWindows} = Main.sessionMode;
+        const sortedItems = [...this._networkItems.values()]
+            .sort((one, two) => one.network.compare(two.network));
 
-        this._itemBox.remove_all_children();
-        this._networks.forEach(network => {
-            this._itemBox.add_child(network.item);
-        });
+        for (const [index, item] of sortedItems.entries())
+            this._itemBox.set_child_at_index(item, index);
 
-        adjustment.value = scrollValue;
+        for (const [net, item] of this._networkItems) {
+            item.visible =
+                hasWindows || net.hasConnections() || net.canAutoconnect();
+        }
     }
 
     _selectNetwork(network) {
-        if (this._selectedNetwork)
-            this._selectedNetwork.item.remove_style_pseudo_class('selected');
-
+        this._networkItems.get(this._selectedNetwork)?.remove_style_pseudo_class('selected');
         this._selectedNetwork = network;
         this._updateSensitivity();
-
-        if (this._selectedNetwork)
-            this._selectedNetwork.item.add_style_pseudo_class('selected');
+        this._networkItems.get(this._selectedNetwork)?.add_style_pseudo_class('selected');
     }
 
     _createNetworkItem(network) {
-        network.item = new NMWirelessDialogItem(network);
-        network.item.setActive(network == this._selectedNetwork);
-        network.item.hide();
-        network.item.connect('selected', () => {
-            Util.ensureActorVisibleInScrollView(this._scrollView, network.item);
+        const item = new NMWirelessDialogItem(network);
+        item.connect('selected', () => {
+            Util.ensureActorVisibleInScrollView(this._scrollView, item);
             this._selectNetwork(network);
         });
-        network.item.connect('destroy', () => {
+        item.connect('destroy', () => {
             let keyFocus = global.stage.key_focus;
-            if (keyFocus && keyFocus.contains(network.item))
+            if (keyFocus && keyFocus.contains(item))
                 this._itemBox.grab_key_focus();
         });
+        return item;
     }
 });